﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Tao.OpenGl;
using SceneLib;

namespace OpenGL
{
    class TerrainGenerator
    {
        private float[,] heights;
        private int size;
        private Random random;
        private SceneMaterial material;

        public TerrainGenerator(float h, float r, int n, SceneMaterial theMaterial)
        {
            size = Convert.ToInt32(Math.Pow(2.0, n)) + 1;
            
            heights = new float[size, size];
            random = new Random();

            generate(n, 0, 0, size - 1, size - 1, r, h);
            material = theMaterial;
        }

        private void generate(int passes, int x0, int y0, int x1, int y1, float r, float h)
        {
            int xm, ym;
            float hm, h1, h2, h3, h4;
            
            if (passes > 0)
            {
                xm = (x0 + x1) / 2;
                ym = (y0 + y1) / 2;
                hm = (heights[x0, y0] + heights[x0, y1] + heights[x1, y0] + heights[x1, y1]) / 4;
                
                h1 = (heights[x0, y0] + heights[x1, y0]) / 2;
                h2 = (heights[x0, y0] + heights[x0, y1]) / 2;
                h3 = (heights[x0, y1] + heights[x1, y1]) / 2;
                h4 = (heights[x1, y0] + heights[x1, y1]) / 2;

                heights[xm, ym] = hm + 2 * r * (Convert.ToSingle(random.NextDouble()) - 0.5f);
                
                heights[xm, y0] = h1 + 2 * r * (Convert.ToSingle(random.NextDouble()) - 0.5f);
                heights[x0, ym] = h2 + 2 * r * (Convert.ToSingle(random.NextDouble()) - 0.5f);
                heights[xm, y1] = h3 + 2 * r * (Convert.ToSingle(random.NextDouble()) - 0.5f);
                heights[x1, ym] = h4 + 2 * r * (Convert.ToSingle(random.NextDouble()) - 0.5f);

                r = r * Convert.ToSingle(Math.Pow(2, -h));

                generate(passes - 1, x0, y0, xm, ym, r, h);
                generate(passes - 1, xm, y0, x1, ym, r, h);
                generate(passes - 1, x0, ym, xm, y1, r, h);
                generate(passes - 1, xm, ym, x1, y1, r, h);
            }
        }

        public void Draw(float x0, float z0, float x1, float z1, float y0)
        {
            Gl.glMaterialfv(Gl.GL_FRONT_AND_BACK, Gl.GL_SPECULAR,
                            MathUtils.GetVector3Array(material.Specular));
            Gl.glMaterialf(Gl.GL_FRONT_AND_BACK, Gl.GL_SHININESS,
                           material.Shininess);
            Gl.glColor3fv(MathUtils.GetVector3Array(material.Diffuse));

            SetTextures(material);

            float hx = (x1 - x0) / size;
            float hz = (z1 - z0) / size;
            float ht = 1.0f / size;

            for (int i = 0; i < size - 1; i++)
            {
                bool sw = false;
                
                Gl.glBegin(Gl.GL_QUAD_STRIP);
                for (int j = 0; j < size; j++)
                {
                    float x, y, z;

                    x = x0 + hx * i;
                    z = z0 + hz * j;
                    y = heights[i, j] + y0;

                    Gl.glNormal3f(0.0f, 1.0f, 0.0f);
                    Gl.glTexCoord2f(i * ht, j * ht);
                    Gl.glVertex3f(x, y, z);
                    
                    x = x0 + hx * (i + 1);
                    y = heights[i + 1, j] + y0;

                    Gl.glNormal3f(0.0f, 1.0f, 0.0f);
                    Gl.glTexCoord2f((i + 1) * ht, j * ht);
                    Gl.glVertex3f(x, y, z);

                    sw = !sw;
                }
                Gl.glEnd();
            }
        }

        private void SetTextures(SceneMaterial sceneMat)
        {
            if (sceneMat.TextureImage != null)
            {

                Gl.glEnable(Gl.GL_TEXTURE_2D);

                sceneMat.CreatePointer();

                Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MAG_FILTER, Gl.GL_LINEAR);
                Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MIN_FILTER, Gl.GL_LINEAR);
                Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_S, Gl.GL_REPEAT);
                Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_T, Gl.GL_REPEAT);

                //Seteo de la textura usada
                Gl.glTexImage2D(Gl.GL_TEXTURE_2D,
                                0,
                                Gl.GL_RGBA,
                                sceneMat.TextureImage.Width,
                                sceneMat.TextureImage.Height,
                                0,
                                Gl.GL_BGR,
                                Gl.GL_UNSIGNED_BYTE,
                                sceneMat.PtrBitmap);
            }
            else
            {
                Gl.glDisable(Gl.GL_TEXTURE_2D);
            }
        }
    }
}
